//
//  main.m
//  mp4Info
//
//  Created by JIENING ZHANG on 2020/11/16.
//

#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCrypto.h>

static int GSmainFuncCallStackNum = 0; // main函数调用栈的层数
static BOOL GSisLittleEnd = NO; // main函数调用栈的层数

int printLineBreakSpaceBycallStack(int k) {
    for(int i=0; i<k; ++i) {
        printf("\n");
    }
    NSArray *callStacks = [NSThread callStackSymbols];
    for(int i=0; i < [callStacks count]-GSmainFuncCallStackNum-1; ++i) {
        printf("    ");
    }
    return 0;
}

int printSpaceBycallStack(void) {
    NSArray *callStacks = [NSThread callStackSymbols];
    for(int i=0; i < [callStacks count]-GSmainFuncCallStackNum-1; ++i) {
        printf("    ");
    }
    return 0;
}

BOOL isBigEndian(void) {
    // 数据，高位→低位，如下例子，01高 02低
    UInt16 testEndian = 0x0102;
    
    // testEndianPtr 指向内存低位地址
    UInt16 *testEndianPtr = &testEndian;
    
    BOOL isBig = YES;

    if ((*(char* )testEndianPtr) == 0x1) {
        // 内存低位地址 对应 高位 字节，这就是 大端
        printf("big Endian\n");
    } else {
        // 内存低位地址 对应 低位 字节，这就是 小端
        printf("little Endian\n");
        isBig = NO;
    }
    
    return isBig;
}

UInt16 reversebytes_uint16t(UInt16 value) {
    UInt16 re =   (value & 0x000000FFU) << 8
                | (value & 0x0000FF00U) >> 8;
    return re;
}

UInt32 reversebytes_uint32t(UInt32 value) {
    UInt32 re =   (value & 0x000000FFU) << 24
                | (value & 0x0000FF00U) << 8
                | (value & 0x00FF0000U) >> 8
                | (value & 0xFF000000U) >> 24;
    return re;
}

UInt64 UInt64reversebytes_uint64t(UInt64 value) {
    UInt64 re =   (value & 0x00000000000000FFU) << 56
                | (value & 0x000000000000FF00U) << 40
                | (value & 0x0000000000FF0000U) << 24
                | (value & 0x00000000FF000000U) << 8
                | (value & 0x000000FF00000000U) >> 8
                | (value & 0x0000FF0000000000U) >> 24
                | (value & 0x00FF000000000000U) >> 40
                | (value & 0xFF00000000000000U) >> 56;
    return re;
}

UInt8 readUInt8FromFile(FILE *fp) {
    UInt8 byteRead = 0;
    size_t readCount = fread(&byteRead, 1, sizeof(byteRead), fp);
    if (readCount != sizeof(byteRead)) {
        return 0;
    }
    return byteRead;
}

UInt16 readUInt16FromFile(FILE *fp) {
    
    UInt16 byteRead2 = 0;
    size_t readCount = fread(&byteRead2, 1, sizeof(byteRead2), fp);
    if (readCount != sizeof(byteRead2)) {
        return 0;
    }
    
    //char *readPtr = (char*)(&byteRead4);
    //printf("byteRead4 byte one by one: 0x%x 0x%x 0x%x 0x%x,  byteRead4 as uint32 %d\n", *readPtr, *(readPtr+1), *(readPtr+2), *(readPtr+3), byteRead4);
    
    if (GSisLittleEnd) {
        UInt32 byteReverse = reversebytes_uint16t(byteRead2);
        //readPtr = (char*)(&byteReverse);
        //printf("byteReverse byte one by one: 0x%x 0x%x 0x%x 0x%x,  byteReverse as uint32 %d \n", *readPtr, *(readPtr+1), *(readPtr+2), *(readPtr+3), byteReverse);
        return byteReverse;
    }
    
    return byteRead2;
}

UInt32 readUInt32FromFile(FILE *fp) {
    
    UInt32 byteRead4 = 0;
    size_t readCount = fread(&byteRead4, 1, sizeof(byteRead4), fp);
    if (readCount != sizeof(byteRead4)) {
        return 0;
    }
    
    //char *readPtr = (char*)(&byteRead4);
    //printf("byteRead4 byte one by one: 0x%x 0x%x 0x%x 0x%x,  byteRead4 as uint32 %d\n", *readPtr, *(readPtr+1), *(readPtr+2), *(readPtr+3), byteRead4);
    
    if (GSisLittleEnd) {
        UInt32 byteReverse = reversebytes_uint32t(byteRead4);
        //readPtr = (char*)(&byteReverse);
        //printf("byteReverse byte one by one: 0x%x 0x%x 0x%x 0x%x,  byteReverse as uint32 %d \n", *readPtr, *(readPtr+1), *(readPtr+2), *(readPtr+3), byteReverse);
        return byteReverse;
    }
    
    return byteRead4;
}

UInt64 readUInt64FromFile(FILE *fp) {
    UInt64 byteRead8 = 0;
    size_t readCount = fread(&byteRead8, 1, sizeof(byteRead8), fp);
    if (readCount != sizeof(byteRead8)) {
        return 0;
    }
    
    //char *readPtr = (char*)(&byteRead4);
    //printf("byteRead4 byte one by one: %x %x %x %x,  byteRead4 as uint32 %d\n", *readPtr, *(readPtr+1), *(readPtr+2), *(readPtr+3), byteRead4);
    
    if (GSisLittleEnd) {
        UInt64 byteReverse = UInt64reversebytes_uint64t(byteRead8);
        //readPtr = (char*)(&byteReverse);
        //printf("byteReverse byte one by one: %x %x %x %x,  byteReverse as uint32 %d \n", *readPtr, *(readPtr+1), *(readPtr+2), *(readPtr+3), byteReverse);
        return byteReverse;
    }
    
    return byteRead8;
}

char* read4CharStrFromFile(FILE *fp) {
    char strWith4Char[4];
    fread(strWith4Char, sizeof(strWith4Char), 1, fp);
    char *str = malloc(5);
    sprintf(str, "%c%c%c%c", strWith4Char[0], strWith4Char[1], strWith4Char[2], strWith4Char[3]);
    return str;
}

@interface Mp4BaseBox : NSObject
@property (nonatomic, copy) NSString* type;
@property (nonatomic, assign) BOOL isLarge;
@property (nonatomic, assign) long boxSize;
@property (nonatomic, assign) long beginFtell;
@property (nonatomic, assign) long headEndFtell;
@property (nonatomic, strong) NSMutableArray *subBoxArr;
@property (nonatomic, assign) FILE *fp;

- (instancetype) initWithCFilePtr:(FILE *)fp;
- (BOOL) read2AddSubBox;
- (BOOL) leafBoxBodyRead;
@end

@implementation Mp4BaseBox

-(instancetype) initWithCFilePtr:(FILE *)fp {
    self = [super init];
    if (self) {
        self.fp = fp;
        self.subBoxArr = [NSMutableArray array];
    }
    return self;
}

- (BOOL) headRead {
    self.beginFtell = ftell(_fp);
    
    UInt64 largeBoxSize = 0;
    UInt32 boxSize = readUInt32FromFile(_fp);
    char* boxType = read4CharStrFromFile(_fp);
    
    if (1 == boxSize) {
        self.isLarge = YES;
        largeBoxSize = readUInt64FromFile(_fp);
        //printf("largeBoxSize: %llu\n", largeBoxSize);
        printSpaceBycallStack();
        printf("boxSize: %d, boxType: %s, largeBoxSize:%llu \n", boxSize, boxType, largeBoxSize);
    }
    if (0 == boxSize) {
        printSpaceBycallStack();
        printf("func boxHeadRead, boxSize == 0, end of file\n");
        return NO;
    }
    if (boxSize < 8) {
        printSpaceBycallStack();
        printf("boxSize=%d < 8, not valid boxsize\n", boxSize);
        return NO;
    }
    
    if(largeBoxSize == 0) {
        largeBoxSize = boxSize;
    }
    
    self.type = [NSString stringWithCString:boxType encoding:NSUTF8StringEncoding];
    self.boxSize = largeBoxSize;
    self.headEndFtell = ftell(_fp);
    printLineBreakSpaceBycallStack(1);
    
    return YES;
}

- (long) getBodySize {
    return self.boxSize - (self.headEndFtell - self.beginFtell);
}

- (BOOL) leafBoxBodyRead {
    long headSize = self.headEndFtell - self.beginFtell;
    long seekLen = self.boxSize - headSize;
    printSpaceBycallStack();
    printf("leafBoxBodyRead: boxType=%s, headSize=%ld, seekLen=%ld\n", [self.type cStringUsingEncoding:NSUTF8StringEncoding], headSize, seekLen);
    fseek(_fp, seekLen, SEEK_CUR);
    //break;
    
    return YES;
}

- (BOOL) containerBoxBodyRead {
    // https://www.jianshu.com/p/529c3729f357
    // Movie Box包含了文件媒体的metadata信息，“moov”是一个container box，具体内容信息在其子box中。一般情况下，“moov”会紧随着“ftyp”。
    // “moov”中包含1个“mvhd”和若干个“trak”。其中“mvhd”是header box，一般作为“moov”的第一个子box出现。“trak”包含了一个track的相关信息，是一个container box。
    
    long currentFileOffest = ftell(_fp);
    long bSize = [self getBodySize];
    while (!feof(_fp)) {
        
        long byteReadInBox = ftell(_fp) - currentFileOffest;
        if (byteReadInBox >= bSize-8) {
            printSpaceBycallStack();
            printf("containerBoxRead: %s, end of box, byteReadInBox %ld, boxSize %lu \n", [self.type cStringUsingEncoding:NSUTF8StringEncoding], byteReadInBox, bSize);
            break;
        }
        
        printSpaceBycallStack();
        printf("containerBoxRead: %s, begin sub, byteReadInBox %ld, boxSize %lu \n", [self.type cStringUsingEncoding:NSUTF8StringEncoding], byteReadInBox, bSize);
        
        if( ! [self read2AddSubBox]) {
            printSpaceBycallStack();
            printf("containerBoxRead, %s, fail 2 read sub, byteReadInBox %ld, boxSize %lu\n", [self.type cStringUsingEncoding:NSUTF8StringEncoding], byteReadInBox, bSize);
            return NO;
        }
    }
    
    return YES;
}

- (BOOL) bodyRead {
    NSArray *containerTypes = @[@"moov", @"trak", @"mdia", @"minf", @"stbl", @"udta"];
    
    if ([containerTypes containsObject:self.type]) {
        return [self containerBoxBodyRead];
    } else {
        return [self leafBoxBodyRead];
    }
    
    return YES;
}

- (BOOL)read2AddSubBox {
    Mp4BaseBox *subBox = [[Mp4BaseBox alloc]initWithCFilePtr:_fp];
    if(! [subBox headRead]) {
        return NO;
    }
    
    NSString *leafSubBoxClassName = [NSString stringWithFormat:@"Mp4%@Box", subBox.type];
    if (NSClassFromString(leafSubBoxClassName)) {
        Mp4BaseBox *leafSubBox = [[NSClassFromString(leafSubBoxClassName) alloc] initWithCFilePtr:_fp];
        
        leafSubBox.type = subBox.type;
        leafSubBox.isLarge = subBox.isLarge;
        leafSubBox.beginFtell = subBox.beginFtell;
        leafSubBox.headEndFtell = subBox.headEndFtell;
        leafSubBox.boxSize = subBox.boxSize;
        
        subBox= nil;
        subBox = leafSubBox;
    }
    
    if(! [subBox bodyRead]) {
        return NO;
    }
    
    [self.subBoxArr addObject:subBox];
    
    return YES;
}

- (void)infoPrint {
    printSpaceBycallStack();
    printf("boxPrint: Type->%s, isLarge->%d, begin->%ld, boxSize->%ld \n"
           ,[self.type cStringUsingEncoding:NSUTF8StringEncoding]
           ,self.isLarge
           ,self.beginFtell
           ,self.boxSize);
}

- (void)loop2PrintInfo {
    [self infoPrint];
    for(Mp4BaseBox *subBox in self.subBoxArr) {
        [subBox loop2PrintInfo];
    }
}



- (BOOL)append:(FILE *)aFilePtr toWritefile:(FILE *)wFilePtr {
    
    UInt32 mdatLenOrigin = 0;
    for(Mp4BaseBox *subBox in self.subBoxArr) {
        if([subBox.type isEqualToString:@"mdat"]) {
            mdatLenOrigin = (UInt32)(subBox.boxSize);
        }
    }
    
    if(mdatLenOrigin == 0) {
        printSpaceBycallStack();
        printf("append toWritefile, empty mdat\n");
        return NO;
    }
    
    fseek(aFilePtr, 0, SEEK_END);
    long afLen = ftell(aFilePtr);
    UInt32 mdatLenWrite = mdatLenOrigin + (UInt32)afLen;
    
    UInt32 mdatLenWriteReverse = reversebytes_uint32t(mdatLenWrite);
    
    printSpaceBycallStack();
    printf("append toWritefile, mdatLenOrigin %u, afLen %ld, mdatLenWrite %u, mdatLenWriteReverse %u\n"
            ,mdatLenOrigin
            ,afLen
            ,mdatLenWrite
            ,mdatLenWriteReverse);

    
    for(Mp4BaseBox *subBox in self.subBoxArr) {
        
        char byteRead[1024];
        char byteR;
        double curProcess = 0.0;
        long count = 0;
        
        long curFose = ftell(_fp);
        if(curFose != subBox.beginFtell) {
            fseek(_fp, subBox.beginFtell, SEEK_SET);
            printSpaceBycallStack();
            printf("append toWritefile, after fseek, curFose: %ld, %s, self.beginFtell %ld, self.boxSize %ld\n"
                    ,curFose
                    ,[subBox.type cStringUsingEncoding:NSUTF8StringEncoding]
                    ,subBox.beginFtell
                    ,subBox.boxSize);
            
        }
        
        if([subBox.type isEqualToString:@"mdat"]) {
            
            readUInt32FromFile(_fp);
            fwrite(&mdatLenWriteReverse, sizeof(mdatLenWriteReverse), 1, wFilePtr);
            count += 4;
        }
        
        {

            printSpaceBycallStack();
            printf("append toWritefile, curFose: %ld, %s, count %ld, self.beginFtell %ld, self.boxSize %ld\n"
                    ,curFose
                    ,[subBox.type cStringUsingEncoding:NSUTF8StringEncoding]
                    ,count
                    ,subBox.beginFtell
                    ,subBox.boxSize);
             

            while(count<subBox.boxSize) {
                if(subBox.boxSize - count >= 1024) {
                    fread(&byteRead, 1, 1024, _fp);
                    fwrite(&byteRead, 1, 1024, wFilePtr);
                    count += 1024;
                } else {
                    fread(&byteR, 1, 1, _fp);
                    fwrite(&byteR, 1, 1, wFilePtr);
                    count += 1;
                }
                

                double process = count/(double)subBox.boxSize;
                if(process - curProcess > 0.1) {
                    curProcess = process;
                    printf("append toWritefile, %s, process %.2f, count %ld \n"
                            ,[subBox.type cStringUsingEncoding:NSUTF8StringEncoding]
                            ,process
                            ,count);
                }
                
            }
        }
        
        if([subBox.type isEqualToString:@"mdat"]) {
            fseek(aFilePtr, 0, SEEK_SET);
            long aCount = 0;
            curProcess = 0.0;
            
            while(aCount < afLen) {
                if(afLen - aCount >= 1024) {
                    fread(&byteRead, 1, 1024, aFilePtr);
                    fwrite(&byteRead, 1, 1024, wFilePtr);
                    aCount += 1024;
                } else {
                    fread(&byteR, 1, 1, aFilePtr);
                    fwrite(&byteR, 1, 1, wFilePtr);
                    aCount += 1;
                }
                

                double process = aCount/(double)afLen;
                if(process - curProcess > 0.1) {
                    curProcess = process;
                    printf("append toWritefile, %s, append process %.2f, aCount %ld \n"
                            ,[subBox.type cStringUsingEncoding:NSUTF8StringEncoding]
                            ,process
                            ,aCount);
                }
                
            }
            
        }
        
    }
    
    
    UInt32 freeBoxLen = 12;
    UInt32 freeBoxLenReverse = reversebytes_uint32t(freeBoxLen);
    fwrite(&freeBoxLenReverse, sizeof(freeBoxLenReverse), 1, wFilePtr);
    
    char *freeType = "free";
    fwrite(freeType, 1, 4, wFilePtr);

    UInt32 mdatLenOriginReverse = reversebytes_uint32t(mdatLenOrigin);
    fwrite(&mdatLenOriginReverse, sizeof(mdatLenOriginReverse), 1, wFilePtr);
       
    printf("append toWritefile, freeBoxLen %u, freeBoxLenReverse %u, mdatLenOrigin %u, mdatLenOriginReverse %u\n"
            ,freeBoxLen
            ,freeBoxLenReverse
            ,mdatLenOrigin
            ,mdatLenOriginReverse);
    
    return YES;
}

@end


@interface Mp4ftypBox : Mp4BaseBox
@end
@implementation Mp4ftypBox
- (BOOL) leafBoxBodyRead {
    // size 4 + type 4 + majorBrand 4 + version 4 + k x compatibleBrands(4) = boxsize
    // ftyp body依次包括1个32位的major brand（4个字符），1个32位的minor version（整数）和 1个以32位（4个字符）为单位元素的数组 compatible brands。这些都是用来指示文件应用级别的信息。
    long bSize = [self getBodySize];
    char* majorBrand = read4CharStrFromFile(self.fp);
    UInt32 minorVersion = readUInt32FromFile(self.fp);
    printSpaceBycallStack();
    printf("ftypBoxRead, majorBrand: %s, minorVersion: %d \n", majorBrand, minorVersion);
    
    UInt32 readCount = 8; // majorBrand(4byte) + version(4byte)
    
    int brandCount = 0;
    while(readCount < bSize) {
        char* compatibleBrand = read4CharStrFromFile(self.fp);
        printSpaceBycallStack();
        printf("ftypBoxRead,  brandCount:%d, compatibleBrand : %s \n", brandCount, compatibleBrand);
        free(compatibleBrand);
        ++ brandCount;
        readCount += 4;
    }

    free(majorBrand);
    return YES;
}
@end

@interface Mp4tkhdBox : Mp4BaseBox
@end
@implementation Mp4tkhdBox
- (BOOL) leafBoxBodyRead {
    
    // https://www.jianshu.com/p/529c3729f357
    //5.5 Track Header Box（tkhd）
    
    UInt32 readCount = 8;
    
    UInt8 version = 0;
    fread(&version, sizeof(version), 1, self.fp);
    readCount+=1;
    
    //24-bit整数，按位或操作结果值，预定义的值(0x000001 ，track_enabled，表示track是有效的）、
    //                                  (0x000002，track_in_movie，表示该track在播放中被使用)、
    //                                  (0x000004，track_in_preview，表示track在预览时被使用。
    UInt8 flags[3];
    fread(&flags, sizeof(flags), 1, self.fp);
    readCount+=3;
    
    UInt8 flagsEnd = flags[2];
    //flagsEnd = 2;
    printSpaceBycallStack();
    printf("tkhdBoxRead, flagsEnd %hd \n", flagsEnd);
    if(flagsEnd & 0x1) {
        printSpaceBycallStack();
        printf("tkhdBoxRead, %hd & 0x000001 ，track_enabled，表示track是有效的 \n", flagsEnd);
    }
    if(flagsEnd & 0x2) {
        printSpaceBycallStack();
        printf("tkhdBoxRead, %hd & 0x000002 ，track_enabled，表示该track在播放中被使用 \n", flagsEnd);
    }
    if(flagsEnd & 0x4) {
        printSpaceBycallStack();
        printf("tkhdBoxRead, %hd & 0x000004 ，track_enabled，表示track在预览时被使用 \n", flagsEnd);
    }
    
    //char *readPtr = (char*)(&byteRead4);
    printSpaceBycallStack();
    printf("tkhdBoxRead, version: 0x%x , flag: 0x%x 0x%x 0x%x \n", version, flags[0], flags[1], flags[2]);
   
    if (version==1) {
        UInt64 creation_time = readUInt64FromFile(self.fp);
        UInt64 modification_time = readUInt64FromFile(self.fp);
        UInt32 track_ID = readUInt32FromFile(self.fp);
        UInt32 reserved = readUInt32FromFile(self.fp);
        UInt64 duration = readUInt64FromFile(self.fp);
        printSpaceBycallStack();
        printf("tkhdBoxRead, version=%d, creation_time: %llu , modification_time: %llu , track_ID: %d , reserved:%d, duration: %llu  \n", version, creation_time, modification_time, track_ID, reserved, duration);
        
        readCount += 32;
    } else {
        // version==0      //通常版本为0
        UInt32 creation_time = readUInt32FromFile(self.fp);   //媒体创建时间 相对于UTC时间1904-01-01零点的秒数）
        UInt32 modification_time = readUInt32FromFile(self.fp); //修改时间
        UInt32 track_ID = readUInt32FromFile(self.fp);
        UInt32 reserved = readUInt32FromFile(self.fp);
        UInt32 duration = readUInt32FromFile(self.fp);//时长
        printSpaceBycallStack();
        printf("tkhdBoxRead, version=%d, creation_time: %ld, modification_time: %ld, track_ID: %ld, reserved:%d, 时长 duration: %ld  \n", version, (long)creation_time, (long)modification_time, (long)track_ID, reserved, (long)duration);
        
        readCount += 20;
    }
    
    UInt32 reserved[2];
    fread(reserved, sizeof(reserved), 1, self.fp);
    
    UInt16 layer = readUInt16FromFile(self.fp);
    
    UInt16 alternate_group = readUInt16FromFile(self.fp);

    UInt8 volumePre = readUInt8FromFile(self.fp);//[8.8] 格式，如果为音频track，1.0（0x0100）表示最大音量；否则为0。
    
    UInt8 volumeSub = readUInt8FromFile(self.fp);
    printf("tkhdBoxRead, layer %d, alternate_group %d, volumePre %d, volumeSub %d \n", layer, alternate_group, volumePre, volumeSub);
    
    UInt16 reserved16 = 0;
    fread(&reserved16, sizeof(reserved16), 1, self.fp);
    
    for(int i=0; i<9; ++i) {
        UInt32 numInMatrix = readUInt32FromFile(self.fp);
        printSpaceBycallStack();
        printf("tkhdBoxRead, 视频变换矩阵 matrix[%i]: %d \n", i, numInMatrix);
    }
    UInt16 widthPre = readUInt16FromFile(self.fp);//宽，[16.16] 格式值。
    UInt16 widthSub = readUInt16FromFile(self.fp);
    UInt32 heightPre = readUInt16FromFile(self.fp);//高，[16.16] 格式值，不必与sample的像素尺寸一致，用于播放时的展示宽高。
    UInt16 heightSub = readUInt16FromFile(self.fp);
    printSpaceBycallStack();
    printf("tkhdBoxRead, widthPre %d, widthSub %d, heightPre %d, heightSub %d \n", widthPre, widthSub, heightPre, heightSub);
    
    return YES;
}
@end

@interface Mp4mvhdBox : Mp4BaseBox
@end
@implementation Mp4mvhdBox
- (BOOL) leafBoxBodyRead {
    
    // https://www.jianshu.com/p/529c3729f357
    // 5.3 Movie Header Box（mvhd）
    //mvhd是个Full Box，引用我第一篇文中解释：“  FullBox，是Box的扩展，Box结构的基础上在Header中增加8bits version和24bits flags
    UInt32 readCount = 8;
    
    UInt8 version = 0;
    fread(&version, sizeof(version), 1, self.fp);
    readCount+=1;
    
    UInt8 flags[3];
    fread(&flags, sizeof(flags), 1,  self.fp);
    readCount+=3;
    
    //char *readPtr = (char*)(&byteRead4);
    printLineBreakSpaceBycallStack(2);
    printf("mvhdBoxRead, version: 0x%x , flag: 0x%x 0x%x 0x%x \n", version, flags[0], flags[1], flags[2]);
   
    if (version==1) {
        UInt64 creation_time = readUInt64FromFile(self.fp);
        UInt64 modification_time = readUInt64FromFile(self.fp);
        UInt32 timescale = readUInt32FromFile(self.fp);
        UInt64 duration = readUInt64FromFile(self.fp);
        printSpaceBycallStack();
        printf("mvhdBoxRead, version=%d, creation_time: %llu , modification_time: %llu , timescale: %d , duration: %llu  \n", version, creation_time, modification_time, timescale, duration);
        
        readCount += 28;
    } else { // version==0      //通常版本为0
        UInt32 creation_time = readUInt32FromFile(self.fp);   //媒体创建时间 相对于UTC时间1904-01-01零点的秒数）
        UInt32 modification_time = readUInt32FromFile(self.fp); //修改时间
        UInt32 timescale = readUInt32FromFile(self.fp);//刻度值,可以理解为1秒的长度
        UInt32 duration = readUInt32FromFile(self.fp);//时长
        printSpaceBycallStack();
        printf("mvhdBoxRead, version=%d, creation_time: %d , modification_time: %d , 1秒的长度 timescale: %d , 时长 duration: %d  \n", version, creation_time, modification_time, timescale, duration);
        
        readCount += 16;
    }
    
    UInt16 rateLow = readUInt16FromFile(self.fp);
    UInt16 rateHigh = readUInt16FromFile(self.fp);
    //typically 1.0  //播放速率,高16和低16分别表示小数点前后整数部分和小数部分
    
    //typically, full volume  //声音,高8位和低8位分别表示小数点前后整数和小数部分
    UInt8 volumeLow;
    fread(&volumeLow, sizeof(volumeLow), 1, self.fp);
    UInt8 volumeHigh;
    fread(&volumeHigh, sizeof(volumeHigh), 1, self.fp);
    printSpaceBycallStack();
    printf("mvhdBoxRead, 播放速率 rateLow: %d , rateHigh: %d , 声音 volumeLow: %d , volumeHigh: %d  \n", rateLow, rateHigh, volumeLow, volumeHigh);
    
    //const bit(16)  reserved = 0;  //2字节保留位
    //const unsigned int(32)[2]  reserved = 0; //4+4字节保留位
    UInt8 reserved[10];
    fread(reserved, sizeof(reserved), 1, self.fp);
    
    //UInt32 matrix[9] ;// Unity matrix 视频变换矩阵
    for(int i=0; i<9; ++i) {
        UInt32 numInMatrix = readUInt32FromFile(self.fp);
        printSpaceBycallStack();
        printf("mvhdBoxRead, 视频变换矩阵 matrix[%i]: %d \n", i, numInMatrix);
    }
    
    //bit(32)[6]  pre_defined = 0;  //6*4保留位
    UInt8 pre_defined[24];
    fread(pre_defined, sizeof(pre_defined), 1, self.fp);
    
    UInt32 next_track_ID = readUInt32FromFile(self.fp); //4字节的下一个track id
    printSpaceBycallStack();
    printf("mvhdBoxRead, next_track_ID %d \n\n", next_track_ID);
    
    return YES;
}
@end


// AES256 nsdata 加密
// https://www.jianshu.com/p/0f941a79ed99
NSData* makeAES256EncryptData(NSData *sourceData, NSString *key) {
    
    char keyPtr[kCCKeySizeAES256 + 1];
    bzero(keyPtr, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
    
    NSUInteger dataLength = [sourceData length];
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    size_t numBytesEncrypted = 0;
    
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding | kCCOptionECBMode,
                                          keyPtr,
                                          kCCBlockSizeAES128,
                                          NULL,
                                          [sourceData bytes],
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &numBytesEncrypted);
    
    if (cryptStatus == kCCSuccess) {
        // 加密成功，从buffer 构建data 返回
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }
    
    free(buffer);
    return nil;
}

NSData* makeAES256EncryptDataFromString(NSString *sourceStr, NSString *key) {
    return makeAES256EncryptData([sourceStr dataUsingEncoding:NSUTF8StringEncoding], key);
}

// AES256 nsdata 解密
// https://www.jianshu.com/p/0f941a79ed99
NSData* makeAES256DecryptData(NSData *encryptData, NSString *key) {
    char keyPtr[kCCKeySizeAES256+1];
    bzero(keyPtr, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
    
    NSUInteger dataLength = [encryptData length];
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    size_t numBytesDecrypted = 0;
    
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding | kCCOptionECBMode,
                                          keyPtr,
                                          kCCBlockSizeAES128,
                                          NULL,
                                          [encryptData bytes],
                                          dataLength,
                                          buffer,
                                          bufferSize,
                                          &numBytesDecrypted);
    
    if (cryptStatus == kCCSuccess) {
        // 解密成功，从buffer 构建data 返回
        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }
    
    free(buffer);
    return nil;
}

NSString* makeAES256DecryptString(NSData *encryptData, NSString *key) {
    NSData* decryptData = makeAES256DecryptData(encryptData, key);
    return [[NSString alloc] initWithData:decryptData encoding:NSUTF8StringEncoding];
}


char* changeCstr(const char* key) {
    if(! key) {
        return NULL;
    }
    size_t inputLen = strlen(key);
    if(0 == inputLen) {
        return NULL;
    }
    
    char* rtnCstr = malloc(inputLen+1);
    for(int j=0; j<inputLen; j+=2) {
        if(j+1 < inputLen) {
            rtnCstr[j+1] = key[j];
            rtnCstr[j] = key[j+1];
        } else {
            rtnCstr[j] = key[j];
        }
    }
    rtnCstr[inputLen] = 0;
    return rtnCstr;
}

bool traversalPath(NSString *path, NSFileManager *fileManager, NSMutableData *data2Write, const char* key) {
    if(! fileManager) {
        fileManager = [NSFileManager defaultManager];
    }
 
    BOOL isDir = NO;
    BOOL isExist = [fileManager fileExistsAtPath:path isDirectory:&isDir];
    if(! isExist) {
        NSLog(@"file/dir not exist : %@", path);
        return false;
    }
 
    NSArray<NSString *> *pathComponts = [path componentsSeparatedByString:@"/"];
    NSString *fName = [pathComponts lastObject];
    if([fName length] == 0) {
        return false;
    }
    NSData *e4FName = makeAES256EncryptDataFromString(fName, [NSString stringWithCString:key encoding:NSUTF8StringEncoding]);
    if([e4FName length] == 0) {
        return false;
    }
    
    uint16 clen=(uint16)(e4FName.length);
    unsigned char low = clen & 0xff;
    unsigned char high = (clen & 0xff00)>>8;
    
    [data2Write appendBytes:&high length:1];
    [data2Write appendBytes:&low length:1];
    [data2Write appendData:e4FName];
    
    printf("clen %d, high %d, low %d, %s\n", clen, high, low, [fName cStringUsingEncoding:NSUTF8StringEncoding]);
    
    unsigned char type = 0;
    
    NSError *err;
    if(isDir) {
        unsigned char type = 0xd;
        [data2Write appendBytes:&type length:1];
        
        NSLog(@"dir : %@", path);
 
        // 获取目录下的文件/目录列表
        NSArray *contentOfFolder = [fileManager contentsOfDirectoryAtPath:path error:&err];
 
        for(NSString *filename in contentOfFolder) {
 
            NSString *subFilePath = [path stringByAppendingPathComponent:filename];
            @autoreleasepool {
                bool subrtn = traversalPath(subFilePath, fileManager, data2Write, key);
                if(! subrtn) {
                    return subrtn;
                }
            }
        }
        
        [data2Write appendBytes:&high length:1];
        [data2Write appendBytes:&low length:1];
        [data2Write appendData:e4FName];
        
        type = 0xe;
        [data2Write appendBytes:&type length:1];
 
    } else {
        NSLog(@"file : %@", path);
        
        type = 0xf;
        [data2Write appendBytes:&type length:1];
        
        NSData *fdata = [NSData dataWithContentsOfFile:path];
        NSData *efdata = makeAES256EncryptData(fdata, [NSString stringWithCString:key encoding:NSUTF8StringEncoding]);
        uint64 efdLen = (uint64)[efdata length];
        
        if([fdata length] > 0 && efdLen == 0) {
            NSLog(@"empty efdata: %@", path);
            return false;
        }
        
//        unsigned char l0 = (efdLen & 0xff);
//        unsigned char l1 = (efdLen & 0xff00)>>8;
//        unsigned char l2 = (efdLen & 0xff0000)>>16;
//        unsigned char l3 = (efdLen & ((uint64)0xff000000))>24;
//        unsigned char l4 = (efdLen & ((uint64)0xff00000000))>>32;
        
        unsigned char l0 = (unsigned char)(efdLen);
        unsigned char l1 = (unsigned char)(efdLen>>8);
        unsigned char l2 = (unsigned char)(efdLen>>16);
        unsigned char l3 = (unsigned char)(efdLen>>24);
        unsigned char l4 = (unsigned char)(efdLen>>32);
        
        if(efdLen > 0xff0000) {
            printf("((uint64)0xff000000)) %lld, \n", ((uint64)0xff000000));
        }
        printf("fdata length %ld, efdLen %ld, l0 %d, l1 %d, l2 %d, l3 %d, l4 %d, \n", (long)[fdata length], (long)efdLen, l0, l1, l2, l3, l4);
        
        [data2Write appendBytes:&l0 length:1];
        [data2Write appendBytes:&l1 length:1];
        [data2Write appendBytes:&l2 length:1];
        [data2Write appendBytes:&l3 length:1];
        [data2Write appendBytes:&l4 length:1];
        
        [data2Write appendData:efdata];
    }
    
    return  true;
}

int inputIntoMcbin(NSString *targetPath, NSString *binFilePath, const char*  key) {
    
    char *head = malloc(64);
    for(int i=0;i<64;++i) {
        //printf("%d %d\n", i, head[i]);
        char r = arc4random() % 120;
        head[i] = r;
        //printf("%d %d\n", i, head[i]);
    }
    
    char* ckey = changeCstr(key);
    
    NSData* eData = makeAES256EncryptDataFromString(
                                                    [NSString stringWithCString:ckey encoding:NSUTF8StringEncoding],
                                                    [NSString stringWithCString:key encoding:NSUTF8StringEncoding]);
    
    NSString *dStr = makeAES256DecryptString(eData, [NSString stringWithCString:key encoding:NSUTF8StringEncoding]);
    char* dkey = changeCstr([dStr cStringUsingEncoding:NSUTF8StringEncoding]);
    
    NSLog(@"dStr=%@, dkey=%s", dStr, dkey);
    
    uint16 clen=(uint16)(eData.length);
    unsigned char low = clen & 0xff;
    unsigned char high = (clen & 0xff00)>>8;
    
    printf("high %d, low %d, \n", high, low);
    head[39] = high;
    head[40] = low;
    
    NSMutableData *data2Write = [NSMutableData data];
    [data2Write appendBytes:head length:64];
    [data2Write appendData:eData];
    
    //NSString *targetPath = @"/Users/mengchang/Desktop";
    
    bool tRes = traversalPath(targetPath, nil, data2Write, key);
    if(! tRes) {
        NSLog(@"traversalPath fail");
        return 1;
    }
    
    //NSString *binFilePath = @"/Users/mengchang/test.mcbin";
     
    BOOL writeRes = [data2Write writeToFile:binFilePath atomically:YES];
    if(writeRes) {
        NSLog(@"wsucc %@", binFilePath);
    } else {
        NSLog(@"wfail %@", binFilePath);
    }
    
    return 0;
}

int outPutFromMcbin(const char* binFilePath, const char* wBase, const char* key) {
//    printf("argc %d, argv0 %s\n", argc, argv[0]);
//    if(argc < 2) {
//        return 1;
//    }
//
//    const char* key=argv[1];
//
//    const char* wBase="/Users/mengchang/mcTest";
//
//    //main2(argc, argv);return 0;
//
//    const char* binFilePath = "/Users/mengchang/test.mcbin";
    
    FILE *fp = NULL;
    fp = fopen(binFilePath, "r");
    if (!fp) {
        printSpaceBycallStack();
        printf("fail to open file to read: %s\n", binFilePath);
        return 1;
    }
    printSpaceBycallStack();
    printf("open file to read: %s\n", binFilePath);
    
    
    long loop=0;
    unsigned char uByteRead=0;
    unsigned char high=0;
    unsigned char low=0;
    uint16 clen=0;
    
    NSMutableData *eData = [NSMutableData data];
    int eloop = -1;
    while (!feof(fp)) {
        fread(&uByteRead, 1, 1, fp);
        if(loop == 39) {
            high = uByteRead;
            clen = ((uint16)high)<<8;
        }
        if(loop == 40) {
            low = uByteRead;
            clen += low;
        }
        if(loop == 64) {
            printf("high %d, low %d, clen %d\n", high, low, clen);
            eloop = 0;
        }
        if(eloop >= 0) {
            [eData appendBytes:&uByteRead length:1];
            ++ eloop;
            if(eloop >= clen) {
                break;
            }
        }
        
        ++ loop;
    }
    
    NSString *keyNSString = [NSString stringWithCString:key encoding:NSUTF8StringEncoding];
    
    NSString *dStr = makeAES256DecryptString(eData, keyNSString);
    char* changeStr = changeCstr([dStr cStringUsingEncoding:NSUTF8StringEncoding]);
    NSLog(@"\n loop=%ld, eloop=%d, eData %@, dStr=%@, changeStr=%s\n", loop, eloop, eData, dStr, changeStr);
    
    if(0 != strcmp(key, changeStr)) {
        printf("key not match \n");
        fclose(fp);
        return 1;
    }
    
    printf("key match \n");
    
    unsigned char highFnameELen=0;
    unsigned char lowFnameELen=0;
    uint16 fNameELen=0;
    char byteRead = 0;
    unsigned char ucharRead = 0;
    int frLoop = 0;
    size_t fReadCount = 0;
    
    uint64 efdLen = 0;
    
    NSMutableArray<NSString *>* wPathArr = [NSMutableArray new];
    [wPathArr addObject:[NSString stringWithCString:wBase encoding:NSUTF8StringEncoding]];
    NSString *dirFullPath = [NSString stringWithCString:wBase encoding:NSUTF8StringEncoding];
    NSString *fileFullPath = nil;
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    bool filework = false;
    filework = true;
    
    while (!feof(fp)) {
        @autoreleasepool {
            
            fReadCount = fread(&highFnameELen, 1, 1, fp);
            if(0 == fReadCount) {
                printf("%d, empty high \n", frLoop);
                break;
            }
            fReadCount = fread(&lowFnameELen, 1, 1, fp);
            if(0 == fReadCount) {
                printf("%d, empty low \n", frLoop);
                break;
            }
            
            fNameELen = ((uint16)highFnameELen)<<8;
            fNameELen += lowFnameELen;
            
            printf("%d, fNameELen %d ,high %d, low %d\n", frLoop, fNameELen, highFnameELen, lowFnameELen);
            
            NSMutableData *md4efname = [NSMutableData new];
            for(uint16 rc=0; rc < fNameELen; ++rc) {
                fReadCount = fread(&byteRead, 1, 1, fp);
                if(0 == fReadCount) {
                    printf("%d, fname read fail \n", frLoop);
                    break;
                }
                [md4efname appendBytes:&byteRead length:1];
            }
            NSString *fname = makeAES256DecryptString(md4efname, keyNSString);
            if([fname length] == 0) {
                printf("%d, empty fname \n", frLoop);
                break;
            }
            
            fread(&byteRead, 1, 1, fp);
            
            if(byteRead == 0xd) {
                [wPathArr addObject:fname];
                dirFullPath = [NSString pathWithComponents:wPathArr];
                
                printf("%d, dirBegin, fNameELen %d, %s, fname %s \n", frLoop, fNameELen, [dirFullPath cStringUsingEncoding:NSUTF8StringEncoding], [fname cStringUsingEncoding:NSUTF8StringEncoding]);
                
                if(filework) {
                    if (![fileManager fileExistsAtPath:dirFullPath]) {
                        [fileManager createDirectoryAtPath:dirFullPath withIntermediateDirectories:YES attributes:nil error:NULL];
                    }
                }
            } else if (byteRead == 0xe) {
                
                
                if(! [fname isEqualToString:[wPathArr lastObject]]) {
                    NSLog(@"%d, dirEnd, not match wPathArr.lastObject %@\n", frLoop, [wPathArr lastObject]);
                    break;
                }
                
                [wPathArr removeLastObject];
                
                dirFullPath = [NSString pathWithComponents:wPathArr];
                
                printf("%d, dirEnd, NameELen %d, fname %s \n", frLoop, fNameELen, [fname cStringUsingEncoding:NSUTF8StringEncoding]);
                
            } else if (byteRead == 0xf) {
                
                fileFullPath = [dirFullPath stringByAppendingPathComponent:fname];
                printf("%d, file, fNameELen %d, %s, fname %s \n", frLoop, fNameELen, [fileFullPath cStringUsingEncoding:NSUTF8StringEncoding], [fname cStringUsingEncoding:NSUTF8StringEncoding]);
                
                fread(&ucharRead, 1, 1, fp);
                efdLen = ucharRead;
                //printf("%d, efdLen %ld, 0-> %d\n", frLoop, (long)efdLen, ucharRead);
                
                fread(&ucharRead, 1, 1, fp);
                efdLen += ((uint64)(ucharRead))<<8;
                //printf("%d, efdLen %ld, 1-> %d\n", frLoop, (long)efdLen, ucharRead);
                
                fread(&ucharRead, 1, 1, fp);
                efdLen += ((uint64)(ucharRead))<<16;
                //printf("%d, efdLen %ld, 2-> %d\n", frLoop, (long)efdLen, ucharRead);
                
                fread(&ucharRead, 1, 1, fp);
                efdLen += ((uint64)(ucharRead))<<24;
                //printf("%d, efdLen %ld, 3-> %d\n", frLoop, (long)efdLen, ucharRead);
                
                fread(&ucharRead, 1, 1, fp);
                efdLen += ((uint64)(ucharRead))<<32;
                //printf("%d, efdLen %ld, 4-> %d\n", frLoop, (long)efdLen, ucharRead);
                
                printf("%d, efdLen %ld \n", frLoop, (long)efdLen);
                
                NSMutableData *edfData = [NSMutableData new];
                for(uint64 bRead=0; bRead < efdLen; ++bRead) {
                    fread(&byteRead, 1, 1, fp);
                    [edfData  appendBytes:&byteRead length:1];
                }
                
                NSData *ddfData = makeAES256DecryptData(edfData, [NSString stringWithCString:key encoding:NSUTF8StringEncoding]);
                printf("%d, ddfData %ld \n", frLoop, (long)[ddfData length]);
                
                if(filework) {
                    [ddfData writeToFile:fileFullPath atomically:YES];
                }
            } else {
                printf("%d, dunkown, fNameELen %d, fname %s \n", frLoop, fNameELen, [fname cStringUsingEncoding:NSUTF8StringEncoding]);
            }
            
            ++ frLoop;
        }
    }
    
    
    fclose(fp);
    return 0;
}

int infoFromMp4(int argc, const char * argv[]) {
    // MP4格式就是由一个个的box组合成的box树，所有的数据都包含在box里，下面来了解一下box的基本结构。一个box是由box header和box里面包含的数据组成的
    // box header中包含了box的大小（size）和类型（type）uint32表示。
    // 其中，size指明了整个box所占用的大小，包括header部分，如果box很大(例如存放具体视频数据的mdat box)，超过了uint32的最大数值，size就被设置为1，并用type后面接下来的8位uint64的largesize来存放大小。
    // box中的字节序为网络字节序，也就是大端字节序（Big-Endian）。
    // x86 系列CPU 都是little-endian 的字节序
    
    // https://www.jianshu.com/p/529c3729f357
    // http://blog.sina.com.cn/s/blog_48f93b530100jz4b.html
    // http://blog.sina.com.cn/s/blog_48f93b530100jz4x.html
    // 标准的box开头的4个字节（32位）为box size，该大小包括box header和box body整个box的大小，这样我们就可以在文件中定位各个box。如果size为1，则表示这个box的大小为large size，真正的size值要在largesize域上得到。（实际上只有“mdat”类型的box才有可能用到large size。）如果size为0，表示该box为文件的最后一个box，文件结尾即为该box结尾。（同样只存在于“mdat”类型的box中。）
    // size后面紧跟的32位为box type，一般是4个字符，如“ftyp”、“moov”等，这些box type都是已经预定义好的，分别表示固定的意义。如果是“uuid”，表示该box为用户扩展类型。如果box type是未定义的，应该将其忽略。
    
    
    const char *filePath = argv[1];
    //const char *filePath = "/Users/mengchang/test.mp4";
    
    FILE *fp = NULL;
    fp = fopen(filePath, "r");
    if (!fp) {
        printSpaceBycallStack();
        printf("fail to open file to read: %s\n", filePath);
        return 1;
    }
    printSpaceBycallStack();
    printf("open file to read: %s\n", filePath);

    Mp4BaseBox *rootBox = [[Mp4BaseBox alloc] initWithCFilePtr:fp];
    rootBox.type=@"rootBox";
    
    while (!feof(fp)) {
        if(! [rootBox read2AddSubBox]) {
            break;
        }
    }
    [rootBox loop2PrintInfo];
    
    printLineBreakSpaceBycallStack(2);
    printf("main func end of file\n");
    
    fclose(fp);  //记得用完关闭文件
    
    return 0;
}

int append2Mp4(int argc, const char * argv[]) {
    
    const char *mp4file = argv[1];
    const char *appendedDir = argv[2];
    NSString *tmpMcbin = [[NSString stringWithCString:appendedDir encoding:NSUTF8StringEncoding] stringByAppendingString:@".mcbin"];
    
    const char *appendedFile = argv[3];
    const char *key = argv[4];
    
    int res = inputIntoMcbin([NSString stringWithCString:appendedDir encoding:NSUTF8StringEncoding]
                             ,tmpMcbin
                             ,key);
    if(0 != res) {
        return res;
    }
    
    
    FILE *fp = NULL;
    fp = fopen(mp4file, "r");
    if (!fp) {
        printSpaceBycallStack();
        printf("fail to open file to read: %s\n", mp4file);
        return 1;
    }
    printSpaceBycallStack();
    printf("open file to read: %s\n", mp4file);

    Mp4BaseBox *rootBox = [[Mp4BaseBox alloc] initWithCFilePtr:fp];
    rootBox.type=@"rootBox";
    
    while (!feof(fp)) {
        if(! [rootBox read2AddSubBox]) {
            break;
        }
    }
    [rootBox loop2PrintInfo];
    
    fseek(fp, 0, SEEK_END);
    rootBox.boxSize = ftell(fp);
    rootBox.beginFtell = 0;
    rootBox.headEndFtell = 0;
    
    fseek(fp, 0, SEEK_SET);
    
    
    FILE *afp = NULL;
    const char* binfile = [tmpMcbin cStringUsingEncoding:NSUTF8StringEncoding];
    afp = fopen(binfile, "r");
    if (!afp) {
        printSpaceBycallStack();
        printf("fail to open binfile to read: %s\n", binfile);
        return 1;
    }
    
    FILE *wfp = NULL;
    wfp = fopen(appendedFile, "w"); //第一个逗号前是文件位置。逗号之后是打开文件方式
    if (!wfp) {
        printSpaceBycallStack();
        printf("fail to open write file to read: %s\n", appendedFile);
        return 1;
    }
    
    [rootBox append:afp toWritefile:wfp];
    
    fclose(afp);
    fclose(wfp);
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error = nil;
    BOOL removeRes = [fileManager removeItemAtPath:tmpMcbin error:&error];
    printSpaceBycallStack();
    printf("tmpMcbin removeRes %d\n", removeRes);
    
    return 0;
}

int cutFromMp4(int argc, const char * argv[]) {
    const char *filePath = argv[1];
    const char *dir4CuttedData = argv[2];
    const char *key = argv[3];
    if(0 == strlen(dir4CuttedData) || 0 == strlen(key)) {
        printSpaceBycallStack();
        printf("invalid argv2 or argv3\n");
        return 1;
    }
    
    NSString *tmpBinfile = [NSString stringWithCString:dir4CuttedData encoding:NSUTF8StringEncoding];
    tmpBinfile = [tmpBinfile stringByAppendingString:@".mcbin"];
    const char* tmpFile4CuttedData = [tmpBinfile cStringUsingEncoding:NSUTF8StringEncoding];
    FILE *wfp = fopen(tmpFile4CuttedData, "wb");
    if (!wfp) {
        printSpaceBycallStack();
        printf("fail to open write file to read: %s\n", tmpFile4CuttedData);
        return 1;
    }
    
    FILE *fp = NULL;
    fp = fopen(filePath, "r");
    if (!fp) {
        printSpaceBycallStack();
        printf("fail to open file to read: %s\n", filePath);
        return 1;
    }
    printSpaceBycallStack();
    printf("open file to read: %s\n", filePath);

    Mp4BaseBox *rootBox = [[Mp4BaseBox alloc] initWithCFilePtr:fp];
    rootBox.type=@"rootBox";
    
    while (!feof(fp)) {
        if(! [rootBox read2AddSubBox]) {
            break;
        }
    }
    
    long mdatLen = 0;
    for(Mp4BaseBox *subBox in rootBox.subBoxArr) {
        if([subBox.type isEqualToString:@"free"]) {
            if(subBox.boxSize == 12) {
                fseek(fp, subBox.headEndFtell, SEEK_SET);
                mdatLen = readUInt32FromFile(fp);
            }
        }
    }
    
    if(0 == mdatLen) {
        printSpaceBycallStack();
        printf("fail to get mdatLen\n");
        return 1;
    }
    
    for(Mp4BaseBox *subBox in rootBox.subBoxArr) {
        if([subBox.type isEqualToString:@"mdat"]) {
            
            long cutLen = subBox.boxSize - mdatLen;
            
            printSpaceBycallStack();
            printf("to get cutLen %ld \n", cutLen);
            
            if(cutLen > 0) {
                fseek(fp, subBox.beginFtell, SEEK_SET);
                fseek(fp, mdatLen, SEEK_CUR);
                
                long count = 0;
                char byteRead[1024];
                char byteR;
                
                while(count<cutLen) {
                    if(cutLen - count >= 1024) {
                        fread(&byteRead, 1, 1024, fp);
                        fwrite(&byteRead, 1, 1024, wfp);
                        count += 1024;
                    } else {
                        fread(&byteR, 1, 1, fp);
                        fwrite(&byteR, 1, 1, wfp);
                        count += 1;
                    }
                }
            }
            
            break;
        }
    }
    
    fclose(wfp);
    
    int outputResponse= outPutFromMcbin(tmpFile4CuttedData, dir4CuttedData, key);
    if(0 != outputResponse) {
        printSpaceBycallStack();
        printf("fail to output\n");
        return outputResponse;
    }
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error = nil;
    BOOL removeRes = [fileManager removeItemAtPath:tmpBinfile error:&error];
    printSpaceBycallStack();
    printf("tmpBinfile removeRes %d\n", removeRes);
    
    return 0;
}

int main(int argc, const char * argv[]) {
    
    NSArray *callStacks = [NSThread callStackSymbols];
    GSmainFuncCallStackNum = (int)[callStacks count];
    
    GSisLittleEnd = !(isBigEndian());
    
    for(int i=0; i<argc; ++i) {
        printf("%d, %s\n", i, argv[i]);
    }
    
    if(2 == argc) {
        return infoFromMp4(argc, argv);
    }
    
    if(4 == argc) {
        return cutFromMp4(argc, argv);
    }
    
    if(5 == argc) {
        return append2Mp4(argc, argv);
    }

//    input from shell pipe or keybord input
//    char stdInputStr[256];
//    scanf("%s", stdInputStr);
//    printf("stdInputStr %s\n", stdInputStr);
    
    return 0;
}
